//= require_tree ./initializers
//= require_tree ./utilities
//= require lib/xss
//= require initializePage
//= require utilities/getImageForLink
//= require @honeybadger-io/js/dist/browser/honeybadger.js
//= require i18n
//= require ahoy.js/dist/ahoy.js

I18n.defaultLocale = 'en';
I18n.locale = document.body.dataset.locale;
I18n.translations = JSON.parse(document.getElementById('i18n-translations').dataset.translations);
const isForemWebview = navigator.userAgent === 'ForemWebView/1';

var instantClick
  , InstantClick = instantClick = function(document, location, $userAgent) {
  // Internal variables
  var $isChromeForIOS = $userAgent.indexOf(' CriOS/') > -1
    , $currentLocationWithoutHash
    , $urlToPreload
    , $preloadTimer
    , $lastTouchTimestamp

  // Preloading-related variables
    , $history = {}
    , $xhr
    , $url = false
    , $mustRedirect = false
    , $fetchedBodies = {}
    , $timing = {}
    , $isPreloading = false
    , $isWaitingForCompletion = false
    , $trackedAssets = []

  // Variables defined by public functions
    , $preloadOnMousedown
    , $delayBeforePreload
    , $eventsCallbacks = {
        fetch: [],
        receive: [],
        wait: [],
        change: [],
        restore: []
      }


  ////////// HELPERS //////////


  function removeHash(url) {
    var index = url.indexOf('#')
    if (index < 0) {
      return url
    }
    return url.substr(0, index)
  }

  function getLinkTarget(target) {
    while (target && target.nodeName != 'A') {
      target = target.parentNode
    }
    return target
  }

  function isNotPreloadable(elem) {
    do {
      if (!elem.hasAttribute) { // Parent of <html>
        break
      }
      if (isForemWebview) {
        return true
      }
      if (elem.hasAttribute('data-instant')) {
        return false
      }
      if (elem.hasAttribute('data-no-instant')) {
        return true
      }
    }
    while (elem = elem.parentNode)
    return false
  }

  function isPreloadable(a) {
    var domain = location.protocol + '//' + location.host

    if (a.target // target="_blank" etc.
        || a.hasAttribute('download')
        || a.href.indexOf(domain + '/') != 0 // Another domain, or no href attribute
        || (a.href.indexOf('#') > -1
            && removeHash(a.href) == $currentLocationWithoutHash) // Anchor
        || isNotPreloadable(a)
       ) {
      return false
    }
    return true
  }

  function triggerPageEvent(eventType, arg1, arg2, arg3) {
    var returnValue = false
    for (var i = 0; i < $eventsCallbacks[eventType].length; i++) {
      if (eventType == 'receive') {
        var altered = $eventsCallbacks[eventType][i](arg1, arg2, arg3)
        if (altered) {
          /* Update args for the next iteration of the loop. */
          if ('body' in altered) {
            arg2 = altered.body
          }
          if ('title' in altered) {
            arg3 = altered.title
          }

          returnValue = altered
        }
      }
      else {
        $eventsCallbacks[eventType][i](arg1, arg2, arg3)
      }
    }
    return returnValue
  }

  function changePage(title, body, newUrl, scrollY, pop) {
    var pageContentDiv = document.getElementById("page-content");
    var memberMenuButton = document.getElementById("member-menu-button");
    var searchTypeahead = document.getElementById("search-typeahead");
    if (memberMenuButton) {
      memberMenuButton.classList.remove('showing')
    }
    if (searchTypeahead) {
      document.getElementById('search-input').value = ''
      searchTypeahead.classList.add('hidden');
    }
    document.body.replaceChild(body, pageContentDiv)

    var prog = document.getElementById("navigation-progress");
    prog.classList.remove("showing");

    if (newUrl) {
      const routeChangeTarget = document.getElementById('route-change-target');
      if(routeChangeTarget) {
        routeChangeTarget.focus();
      }
      document.getElementById('page-route-change').textContent = title;
      history.pushState(null, null, newUrl.replace("?samepage=true","").replace("&samepage=true",""))

      var hashIndex = newUrl.indexOf('#'),
          hashElem = hashIndex > -1 && (
            document.getElementById(newUrl.substr(hashIndex + 1)) ||
            document.querySelector(`[name=${newUrl.substr(hashIndex + 1)}].anchor`)
          ),
          offset = 0,
          samePage = newUrl.indexOf("samepage=true") > -1;

      if (hashElem) {
        while (hashElem.offsetParent) {
          offset += hashElem.offsetTop

          hashElem = hashElem.offsetParent
        }
      }
      if (!samePage){
        scrollTo(0, offset)
      }


      $currentLocationWithoutHash = removeHash(newUrl)
    }
    else {
      scrollTo(0, scrollY)
    }

    if ($isChromeForIOS && document.title == title) {
      /* Chrome for iOS:
       *
       * 1. Removes title on pushState, so the title needs to be set after.
       *
       * 2. Will not set the title if it's identical when trimmed, so
       *    appending a space won't do; but a non-breaking space works.
       */
      document.title = title + String.fromCharCode(160)
    }
    else {
      document.title = title
    }

    instantanize()
    if (pop) {
      triggerPageEvent('restore')
    }
    else {
      triggerPageEvent('change', false)
    }
  }

  function setPreloadingAsHalted() {
    $isPreloading = false
    $isWaitingForCompletion = false
  }

  function removeNoscriptTags(html) {
    /* Must be done on text, not on a node's innerHTML, otherwise strange
     * things happen with implicitly closed elements (see the Noscript test).
     */
    return html.replace(/<noscript[\s\S]+?<\/noscript>/gi, '')
  }


  ////////// EVENT LISTENERS //////////


  function mousedownListener(e) {
    if ($lastTouchTimestamp > (+new Date - 500)) {
      return // Otherwise, click doesn't fire
    }

    var a = getLinkTarget(e.target)

    if (!a || !isPreloadable(a)) {
      return
    }

    preload(a.href)
  }

  function mouseoverListener(e) {
    if ($lastTouchTimestamp > (+new Date - 500)) {
      return // Otherwise, click doesn't fire
    }

    var a = getLinkTarget(e.target)

    if (!a || !isPreloadable(a)) {
      return
    }

    a.addEventListener('mouseout', mouseoutListener)

    if (!$delayBeforePreload) {
      preload(a.href)
    }
    else {
      $urlToPreload = a.href
      $preloadTimer = setTimeout(preload, $delayBeforePreload)
    }
    getImageForLink(a);
  }

  function touchstartListener(e) {
    $lastTouchTimestamp = +new Date
    var a = getLinkTarget(e.target)

    if (!a || !isPreloadable(a)) {
      return
    }

    if ($preloadOnMousedown) {
      a.removeEventListener('mousedown', mousedownListener)
    }
    else {
      a.removeEventListener('mouseover', mouseoverListener)
    }
    preload(a.href);
    getImageForLink(a);
  }

  // If a link is focused, it is preloaded just like on mouseover.
  // It also covers the issue where a user needs to press <return>
  // twice in order to follow a focused link.
  function focusListener(e) {
    var a = getLinkTarget(e.target)

    if (!a || !isPreloadable(a)) {
      return
    }

    if (!$delayBeforePreload) {
      preload(a.href)
    }
    else {
      $urlToPreload = a.href
      $preloadTimer = setTimeout(preload, $delayBeforePreload)
    }
    getImageForLink(a);
  }

  function clickListener(e) {
    try{
      var a = getLinkTarget(e.target)

      if (!a || !isPreloadable(a)) {
        return
      }

      if (e.which > 1 || e.metaKey || e.ctrlKey) { // Opening in new tab
        return
      }
      display(a.href);
      e.preventDefault();
    }
    catch(err){
      console.log(err);
    }
  }

  function mouseoutListener() {
    if ($preloadTimer) {
      clearTimeout($preloadTimer)
      $preloadTimer = false
      return
    }

    if (!$isPreloading || $isWaitingForCompletion) {
      return
    }
    $xhr.abort()
    setPreloadingAsHalted()
  }

  function readystatechangeListener() {
    processXHR($xhr,$url);
  }

  function processXHR(xhr,url) {
    if (xhr.readyState < 4) {
      return
    }
    if (xhr.status == 0) {
      /* Request aborted */
      return
    }

    $timing.ready = +new Date - $timing.start
    var pageContentDiv = document.getElementById("page-content");
    if (pageContentDiv && xhr.status === 200 && xhr.getResponseHeader('Content-Type').match(/\/(x|ht|xht)ml/)) {
      var doc = document.implementation.createHTMLDocument('');
      doc.documentElement.innerHTML = removeNoscriptTags(xhr.responseText)
      var title = doc.title
      var body = doc.getElementById("page-content")
      var alteredOnReceive = triggerPageEvent('receive', url, body, title)
      if (alteredOnReceive) {
        if ('body' in alteredOnReceive) {
          body = alteredOnReceive.body
        }
        if ('title' in alteredOnReceive) {
          title = alteredOnReceive.title
        }
      }
      $fetchedBodies[url] = {body:body, title:title};
      var urlWithoutHash = removeHash(url)

      var elems = doc.head.children
        , found = 0
        , elem
        , data

      for (var i = 0; i < elems.length; i++) {
        elem = elems[i]
        if (elem.hasAttribute('data-instant-track')) {
          data = elem.getAttribute('href') || elem.getAttribute('src') || elem.innerHTML
          for (var j = 0; j < $trackedAssets.length; j++) {
            if ($trackedAssets[j] == data) {
              found++
            }
          }
        }
      }
      if (found != $trackedAssets.length) {
        $mustRedirect = true // Assets have changed
      }
    }
    else {
      $mustRedirect = true // Not an HTML document
    }

    if ($isWaitingForCompletion && $url === url) {
      $isWaitingForCompletion = false
      display($url)
    }
  }

  function popstateListener() {
    var loc = removeHash(location.href)
    if (loc == $currentLocationWithoutHash) {
      return
    }

    if (!(loc in $history)) {
      location.href = location.href
      return
    }
    $history[$currentLocationWithoutHash] = {
      body: document.getElementById("page-content"),
      title: document.title,
      scrollY: pageYOffset
    }

    $currentLocationWithoutHash = loc
    changePage($history[loc].title, $history[loc].body, false, $history[loc].scrollY, true)
  }


  ////////// MAIN FUNCTIONS //////////


  function instantanize(isInitializing) {
    var docBody = document.body;
    if (docBody) {
      document.body.addEventListener('touchstart', touchstartListener, true)
      document.body.addEventListener('focus', focusListener, true)
      if ($preloadOnMousedown) {
        document.body.addEventListener('mousedown', mousedownListener, true)
      } else {
        document.body.addEventListener('mouseover', mouseoverListener, true)
      }
      document.body.addEventListener('click', clickListener, true)
    }

    if (!isInitializing) {
      var scriptElementsInDOM = document.body.getElementsByTagName('script')
        , scriptElementsToCopy = []
        , originalElement
        , copyElement
        , parentNode
        , nextSibling
        , i

      // `scriptElementsInDOM` will change during the copy of scripts if
      // a script add or delete script elements, so we need to put script
      // elements in an array to loop through them correctly.
      for (i = 0; i < scriptElementsInDOM.length; i++) {
        // gist liquid tags are handled seperately via app/javascript/utilities/gist.js
        if(scriptElementsInDOM[i].id === 'gist-ltag') continue;
        scriptElementsToCopy.push(scriptElementsInDOM[i])
      }

      for (i = 0; i < scriptElementsToCopy.length; i++) {
        originalElement = scriptElementsToCopy[i]
        if (!originalElement) { // Might have disappeared, see previous comment
          continue
        }
        if (originalElement.hasAttribute('data-no-instant')) {
          continue
        }

        copyElement = document.createElement('script')
        for (var j = 0; j < originalElement.attributes.length; j++) {
          copyElement.setAttribute(originalElement.attributes[j].name, originalElement.attributes[j].value)
        }
        copyElement.textContent = originalElement.textContent

        parentNode = originalElement.parentNode
        nextSibling = originalElement.nextSibling
        parentNode.removeChild(originalElement)
        parentNode.insertBefore(copyElement, nextSibling)
      }
    }
  }

  function preload(url, option) {
    if (!$preloadOnMousedown
        && 'display' in $timing
        && +new Date - ($timing.start + $timing.display) < 100) {
      return
    }
    if ($preloadTimer) {
      clearTimeout($preloadTimer)
      $preloadTimer = false
    }

    if (!url) {
      url = $urlToPreload
    }

    if ($isPreloading && (url == $url || $isWaitingForCompletion)) {
      return
    }
    $isPreloading = true
    $isWaitingForCompletion = false

    $mustRedirect = false
    $timing = {
      start: +new Date
    }
    if (url.indexOf("?") == -1) {
      var internalUrl = url+"?i=i"
    }
    else {
      var internalUrl = url+"&i=i"
    }
    removeExpiredKeys()
    triggerPageEvent('fetch')
    if (!$fetchedBodies[url]){
      if (option === "force") {
        getURLInfo(url, function () {
          processXHR(this,url);
        })
      }
      else {
        $url = url
        $xhr.open('GET', internalUrl)
        $xhr.send()
      }
    }
  }

  function removeExpiredKeys(option) {
    if ( Object.keys($fetchedBodies).length > 13 || option == "force" ) {
      $fetchedBodies = {};
    }

  }

  function getURLInfo(url, callback) {
    var xhr = new XMLHttpRequest();
    if (url.indexOf("?") == -1) {
      var internalUrl = url+"?i=i"
    }
    else {
      var internalUrl = url+"&i=i"
    }
    xhr.open (
      "GET",
      internalUrl,
      true
    );
    xhr.onreadystatechange = function() {
      if (xhr.readyState == 4) {
        // defensive check
        if (typeof callback == "function") {
          // apply() sets the meaning of "this" in the callback
          callback.apply(xhr);
        }
      }
    }
    // send the request *after* the event handler is defined
    xhr.send();
  }

  function display(url) {
    $url = url;
    if($fetchedBodies[url]){
      var body = $fetchedBodies[url]['body'];
      var title = $fetchedBodies[url]['title'];
    }
    else {
      var prog = document.getElementById("navigation-progress");
      prog.classList.add("showing");
    }

    if (!('display' in $timing)) {
      $timing.display = +new Date - $timing.start
    }
    if ($preloadTimer || !$isPreloading) {
      if ($preloadTimer && $url && $url != url) {
        location.href = url
        return
      }
      preload(url)
      triggerPageEvent('wait')
      $isWaitingForCompletion = true // Must be set *after* calling `preload`
      return
    }
    if ($isWaitingForCompletion) {
      /* The user clicked on a link while a page was preloading. Either on
         the same link or on another link. If it's the same link something
         might have gone wrong (or he could have double clicked, we don't
         handle that case), so we send him to the page without pjax.
         If it's another link, it hasn't been preloaded, so we redirect the
         user to it.
      */
      location.href = url
      return
    }
    if ($mustRedirect) {
      location.href = $url
      return
    }

    if (!body) {
      triggerPageEvent('wait')
      $isWaitingForCompletion = true
      return
    }
    $history[$currentLocationWithoutHash] = {
      body: document.getElementById("page-content"),
      title: document.title,
      scrollY: pageYOffset
    }
    setPreloadingAsHalted()
    changePage(title, body, $url)
  }


  ////////// PUBLIC VARIABLE AND FUNCTIONS //////////

  var supported = 'pushState' in history
                  && (!$userAgent.match('Android') || $userAgent.match('Chrome/') || $userAgent.match('Firefox/'))
                  && location.protocol != "file:"

  /* The (sad) state of Android's AOSP browsers:

     2.3.7: pushState appears to work correctly, but
            `doc.documentElement.innerHTML = body` is buggy.
            Update: InstantClick doesn't use that anymore, but it may
            fail where 3.0 do, this needs testing again.

     3.0:   pushState appears to work correctly (though the address bar is
            only updated on focus), but
            `document.documentElement.replaceChild(doc.body, document.body)`
            throws DOMException: WRONG_DOCUMENT_ERR.

     4.0.2: Doesn't support pushState.

     4.0.4,
     4.1.1,
     4.2,
     4.3:   Claims support for pushState, but doesn't update the address bar.

     4.4:   Works correctly. Claims to be 'Chrome/30.0.0.0'.

     All androids tested with Android SDK's Emulator.
     Version numbers are from the browser's user agent.

     Because of this mess, the only allowed browser on Android is Chrome.
  */

  function init(preloadingMode) {
    if ($currentLocationWithoutHash) {
      /* Already initialized */
      return
    }
    if (!supported) {
      triggerPageEvent('change', true)
      return
    }

    if (preloadingMode == 'mousedown') {
      $preloadOnMousedown = true
    }
    else if (typeof preloadingMode == 'number') {
      $delayBeforePreload = preloadingMode
    }
    $currentLocationWithoutHash = removeHash(location.href)
    $history[$currentLocationWithoutHash] = {
      body: document.getElementById("page-content"),
      title: document.title,
      scrollY: pageYOffset
    }

    var elems = document.head.children
      , elem
      , data
    for (var i = 0; i < elems.length; i++) {
      elem = elems[i]
      if (elem.hasAttribute('data-instant-track')) {
        data = elem.getAttribute('href') || elem.getAttribute('src') || elem.innerHTML
        /* We can't use just `elem.href` and `elem.src` because we can't
           retrieve `href`s and `src`s from the Ajax response.
        */
        $trackedAssets.push(data)
      }
    }

    $xhr = new XMLHttpRequest()
    $xhr.addEventListener('readystatechange', readystatechangeListener)

    instantanize(true)

    triggerPageEvent('change', true)

    addEventListener('popstate', popstateListener)
    addRefreshBehavior();
  }

  function on(eventType, callback) {
    $eventsCallbacks[eventType].push(callback)
  }

  function addRefreshBehavior(){
    if (!("ontouchstart" in document.documentElement)) {
      return
    }

    var script = document.createElement('script');
    script.src = "<%= javascript_path 'lib/pulltorefresh.js' %>";
    document.head.appendChild(script);
    var waitingOnPTR = setInterval(function(){
      if (typeof PullToRefresh !== 'undefined') {
        var ptr = PullToRefresh.init({
          mainElement: 'body',
          passive: true,
          onRefresh: function(){
            window.location.reload();
            }
         });
         clearInterval(waitingOnPTR)
      }
    }, 1)
  }


  ////////////////////


  return {
    supported: supported,
    init: init,
    isPreloadable: isPreloadable,
    preload: preload,
    removeExpiredKeys: removeExpiredKeys,
    display: display,
    on: on
  }

}(document, location, navigator.userAgent);


// FUNCTIONAL CODE FOR PAGE

  function initializeBaseApp() {
    InstantClick.on('change', function() {
      initializePage();
    });
    InstantClick.init();
  }

// INITIALIZE/ERROR HANDLING

  Honeybadger.configure({
    apiKey: document.body.dataset.honeybadgerKey,
    environment: "<%= Rails.env %>",
    revision: document.body.dataset.releaseFootprint,
  });

  Honeybadger.beforeNotify(function(notice) {
    if (!notice || typeof notice.message !== 'string') return true
    const ignorePatterns = [/ResizeObserver/i, /MetaMask/i, /MtPopUpList/i, /ChunkLoadError/i]
    return !(ignorePatterns.some((pattern) => pattern.test(notice.message)));
  });

// INITIALIZE AHOY TRACKING
// Setting cookies to false matches what we do in ahoy's initializer.
// Setting trackVisits to false prevents ahoy from creating a visit on the server-side.
  ahoy.configure({
    cookies: false,
    trackVisits: false
  });

// Start BaseApp for Page
  initializeBaseApp()
